Preserve staged counterpart display names in Raven identity repairs#471
Preserve staged counterpart display names in Raven identity repairs#471
Conversation
Agent-Logs-Url: https://github.com/DHCross/Shipyard/sessions/bd1e4e3a-b5f9-4f5d-a4a8-681bc34c2943 Co-authored-by: DHCross <45954119+DHCross@users.noreply.github.com>
Agent-Logs-Url: https://github.com/DHCross/Shipyard/sessions/bd1e4e3a-b5f9-4f5d-a4a8-681bc34c2943 Co-authored-by: DHCross <45954119+DHCross@users.noreply.github.com>
Agent-Logs-Url: https://github.com/DHCross/Shipyard/sessions/bd1e4e3a-b5f9-4f5d-a4a8-681bc34c2943 Co-authored-by: DHCross <45954119+DHCross@users.noreply.github.com>
Agent-Logs-Url: https://github.com/DHCross/Shipyard/sessions/bd1e4e3a-b5f9-4f5d-a4a8-681bc34c2943 Co-authored-by: DHCross <45954119+DHCross@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
There was a problem hiding this comment.
Pull request overview
This PR hardens Raven’s staged counterpart identity handling so “active staged counterpart” slots only render when a usable canonical display name is available, and identity-repair flows avoid generic placeholder substitutions.
Changes:
- Added shared counterpart identity utilities to classify/reject generic placeholder labels and to validate usable counterpart display names.
- Updated staged profile snapshotting to resolve names from
canonicalName → displayName → name, exclude unnamed/generic observer slots, and propagate richer name fields through the API payload/type surface. - Updated provenance repair + entity guard behavior to emit corrections when challenged names match the active staged counterpart, and to return a repair message when a staged slot exists without a usable name (with regression tests).
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| vessel/src/lib/raven/stagedProfileSnapshot.ts | Resolve counterpart display name from canonical/display/name and drop unnamed/generic observer slots. |
| vessel/src/lib/raven/counterpartIdentity.ts | New helper functions/pattern for rejecting generic counterpart labels. |
| vessel/src/lib/raven/tests/stagedProfileSnapshot.test.ts | Regression tests for displayName recovery and unnamed counterpart exclusion. |
| vessel/src/hooks/useOracleChat.ts | Include displayName/canonicalName in client payloads. |
| vessel/src/app/api/raven-chat/types.ts | Extend Profile type with displayName/canonicalName. |
| vessel/src/app/api/raven-chat/route.ts | Thread “staged counterpart slot present” signal into repair + entity guard. |
| vessel/src/app/api/raven-chat/requestParsing.ts | Allowlist displayName/canonicalName in profile sanitization. |
| vessel/src/app/api/raven-chat/entityGuard.ts | Pass staged-slot-present signal through to provenance repair reply generation. |
| vessel/src/app/api/raven-chat/counterpartProvenance.ts | Use usable-name validation; add correction + “slot present but unnamed” repair messaging. |
| vessel/src/app/api/raven-chat/tests/requestParsing.test.ts | Test that sanitizeProfile preserves displayName/canonicalName. |
| vessel/src/app/api/raven-chat/tests/counterpartProvenance.test.ts | Tests for correction behavior and placeholder-rejection repair replies. |
| vessel/sherlog-velocity/data/session-log.jsonl | Sherlog telemetry log update. |
| vessel/sherlog-velocity/data/self-model.json | Sherlog model artifact update. |
| vessel/sherlog-velocity/data/gap-history.jsonl | Sherlog gap history update. |
| vessel/sherlog-velocity/data/active-session.json | Remove active session artifact. |
| return { | ||
| profileId: asTrimmedText(profile.profileId, 160) || asTrimmedText(profile.id, 160), | ||
| name: asTrimmedText(profile.name, 120), | ||
| displayName: asTrimmedText(profile.displayName, 120), | ||
| canonicalName: asTrimmedText(profile.canonicalName, 120), |
There was a problem hiding this comment.
sanitizeProfile() now allowlists displayName/canonicalName, but mergeHydratedProfile() (used after vault hydration) does not merge these fields. This means the new identity fields can be dropped whenever a staged profile is hydrated/merged, defeating the preservation/repair flow. Consider extending mergeHydratedProfile() to carry displayName/canonicalName through the same way name is handled (prefer explicit profile values, otherwise fallback).
|
|
||
| const orderedProfiles = rawOrdered.filter((entry) => { | ||
| if (!entry.displayName) { | ||
| excludedNames.push(entry.profile.profileId || 'unnamed staged counterpart'); |
There was a problem hiding this comment.
excludedNames is documented as a list of excluded names, but this new branch pushes profileId when displayName is empty. That changes the meaning/shape of excludedNames (IDs vs names) and could break callers that log/surface it as a user-facing backstage notice. Either push a consistently name-like label here (e.g. a stable placeholder) or update the type/docs (and any consumers) to explicitly allow IDs in excludedNames.
| excludedNames.push(entry.profile.profileId || 'unnamed staged counterpart'); | |
| excludedNames.push('unnamed staged counterpart'); |
| (candidate) => String(candidate.role || '').trim().toUpperCase() !== 'SELF', | ||
| ) || Boolean(stagedProfileSnapshot.activeCounterpart); | ||
| const polyadicCounterpartProfiles = stagedProfiles.filter( | ||
| (candidate) => String(candidate.role || '').trim().toUpperCase() !== 'SELF', |
There was a problem hiding this comment.
stagedCounterpartSlotPresent uses role !== 'SELF' to detect a counterpart slot, but sanitizeProfile() normalizes role to 'SELF' | 'OBSERVER' | ''. With the current check, an invalid/empty role ('') will be treated as a staged counterpart slot, which can incorrectly trigger the “slot present but name missing” repair path. Consider checking explicitly for 'OBSERVER' (and similarly wherever counterpart presence is inferred) so invalid roles don’t masquerade as a counterpart slot.
| (candidate) => String(candidate.role || '').trim().toUpperCase() !== 'SELF', | |
| ) || Boolean(stagedProfileSnapshot.activeCounterpart); | |
| const polyadicCounterpartProfiles = stagedProfiles.filter( | |
| (candidate) => String(candidate.role || '').trim().toUpperCase() !== 'SELF', | |
| (candidate) => String(candidate.role || '').trim().toUpperCase() === 'OBSERVER', | |
| ) || Boolean(stagedProfileSnapshot.activeCounterpart); | |
| const polyadicCounterpartProfiles = stagedProfiles.filter( | |
| (candidate) => String(candidate.role || '').trim().toUpperCase() === 'OBSERVER', |



Raven could retain an active staged counterpart slot while losing the counterpart’s usable name, producing contradictory replies like “Stephie is not staged” followed by “the active staged counterpart is the staged counterpart.” The staged counterpart slot should only render with a canonical display name, or fail into a repairable state.
Counterpart identity preservation
displayNameandcanonicalNamethrough client payloads, request sanitization, and server-sideProfiletyping.canonicalName → displayName → name.Generic placeholder rejection
the staged counterpartactive staged counterpartProfile 3unknownRepair/correction behavior
DH Cross + Stephieanchoring.Stephie is not a staged profilethe staged counterpartas primary identityactive staged counterpart here is the staged counterpart